/*
 * 硬解解码包括CUDA/QSV/DRM/VAAPI/VDPAU等等，CUDA很好理解，你主要装了NVIDIA显卡就可以使用；
 * QSV是Intel CPU自带的解码器，但是现在大部分CPU不带QSV支持（便宜），而且配置也不方便.
 * 编译时需安装Intel Media SDK，屏蔽头文件包含后可编译通过但运行不了
 */

#include "hwddecodethread.h"
#include <QDebug>

typedef struct DecodeContext{
    AVBufferRef *hw_device_ref;
}DecodeContext;

DecodeContext decode = {NULL};

static enum AVPixelFormat hw_pix_fmt;
static AVBufferRef* hw_device_ctx=NULL;

HwdDecodeThread::HwdDecodeThread()
{

}

HwdDecodeThread::~HwdDecodeThread()
{

}

void HwdDecodeThread::set_path(QString url)
{
    mFilePath=url;
}

void HwdDecodeThread::ffmpeg_init_variables()
{
    avformat_network_init();
    mFmtCtx = avformat_alloc_context();
    mPkt = av_packet_alloc();
    mYUVFrame = av_frame_alloc();
    mRGBFrame = av_frame_alloc();
    mNV12Frame = av_frame_alloc();

    mInitFlag=true;
}

void HwdDecodeThread::ffmpeg_free_variables()
{
    if(!mPkt)
        av_packet_free(&mPkt);
    if(!mYUVFrame)
        av_frame_free(&mYUVFrame);
    if(!mRGBFrame)
        av_frame_free(&mRGBFrame);
    if(!mNV12Frame)
        av_frame_free(&mNV12Frame);
    if(!mVideoCodecCtx)
        avcodec_free_context(&mVideoCodecCtx);
    if(!mVideoCodecCtx)
        avcodec_close(mVideoCodecCtx);
    if(!mFmtCtx)
        avformat_close_input(&mFmtCtx);
}

bool HwdDecodeThread::open_input_file()
{
    if(!mInitFlag){
        ffmpeg_init_variables();
        qDebug()<<"init variables done";
    }

    enum AVHWDeviceType type;
    int i;

    type = av_hwdevice_find_type_by_name("cuda");
    if (type == AV_HWDEVICE_TYPE_NONE) {
        fprintf(stderr, "Device type %s is not supported.\n", "h264_cuvid");
        fprintf(stderr, "Available device types:");
        while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
            fprintf(stderr, " %s", av_hwdevice_get_type_name(type));
        fprintf(stderr, "\n");
        return 0;
    }

    /* open the input file */
    if (avformat_open_input(&mFmtCtx, mFilePath.toLocal8Bit().data(), NULL, NULL) != 0) {
        return 0;
    }

    if (avformat_find_stream_info(mFmtCtx, NULL) < 0) {
        fprintf(stderr, "Cannot find input stream information.\n");
        return 0;
    }

    /* find the video stream information */
    mRet = av_find_best_stream(mFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, (AVCodec**)&mVideoCodec, 0);
    if (mRet < 0) {
        fprintf(stderr, "Cannot find a video stream in the input file\n");
        return 0;
    }
    mVideoStreamIndex = mRet;

    //获取支持该decoder的hw配置型
    for (i = 0;; i++) {
        const AVCodecHWConfig *config = avcodec_get_hw_config(mVideoCodec, i);
        if (!config) {
            fprintf(stderr, "Decoder %s does not support device type %s.\n",
                    mVideoCodec->name, av_hwdevice_get_type_name(type));
            return 0;
        }
        if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
                config->device_type == type) {
            hw_pix_fmt = config->pix_fmt;
            break;
        }
    }

    if (!(mVideoCodecCtx = avcodec_alloc_context3(mVideoCodec)))
        return 0;//return AVERROR(ENOMEM);

    mVideoStream = mFmtCtx->streams[mVideoStreamIndex];
    if (avcodec_parameters_to_context(mVideoCodecCtx, mVideoStream->codecpar) < 0)
        return 0;

    mVideoCodecCtx->get_format  = get_hw_format;

    if (hw_decoder_init(mVideoCodecCtx, type) < 0)
        return 0;

    if ((mRet = avcodec_open2(mVideoCodecCtx, mVideoCodec, NULL)) < 0) {
        fprintf(stderr, "Failed to open codec for stream #%u\n", mVideoStreamIndex);
        return 0;
    }

    mImgCtx = sws_getContext(mVideoCodecCtx->width,
                             mVideoCodecCtx->height,
                             AV_PIX_FMT_NV12,
                             mVideoCodecCtx->width,
                             mVideoCodecCtx->height,
                             AV_PIX_FMT_RGB32,
                             SWS_BICUBIC,NULL,NULL,NULL);

    mNumBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32,mVideoCodecCtx->width,mVideoCodecCtx->height,1);
    mOutBuffer = (unsigned char *)av_malloc(mNumBytes*sizeof(uchar));

    int res = av_image_fill_arrays(
                mRGBFrame->data,mRGBFrame->linesize,
                mOutBuffer,AV_PIX_FMT_RGB32,
                mVideoCodecCtx->width,mVideoCodecCtx->height,1);
    if(res<0){
        qDebug()<<"Fill arrays failed.";
        return 0;
    }

    mOpenFlag=true;
    return true;
}

AVPixelFormat HwdDecodeThread::get_hw_format(AVCodecContext *ctx, const AVPixelFormat *pix_fmts)
{
    Q_UNUSED(ctx)
    const enum AVPixelFormat *p;

    for (p = pix_fmts; *p != -1; p++) {
        if (*p == hw_pix_fmt)
            return *p;
    }

    fprintf(stderr, "Failed to get HW surface format.\n");
    return AV_PIX_FMT_NONE;
}

int HwdDecodeThread::hw_decoder_init(AVCodecContext *ctx, const AVHWDeviceType type)
{
    int err = 0;

    if ((err = av_hwdevice_ctx_create(&hw_device_ctx, type,
                                      NULL, NULL, 0)) < 0) {
        fprintf(stderr, "Failed to create specified HW device.\n");
        return err;
    }
    ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);

    return err;
}

void HwdDecodeThread::stop_thread()
{
    mStopFlag=true;
}

void HwdDecodeThread::run()
{
    if(!mOpenFlag){
        open_input_file();
    }

    while(av_read_frame(mFmtCtx,mPkt)>=0)
    {
        if(mStopFlag)
            break;
        if(mPkt->stream_index == mVideoStreamIndex){
            if(avcodec_send_packet(mVideoCodecCtx,mPkt)>=0){
                int ret;
                while((ret=avcodec_receive_frame(mVideoCodecCtx,mYUVFrame))>=0){
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                        return;
                    else if (ret < 0) {
                        fprintf(stderr, "Error during decoding\n");
                        exit(1);
                    }

                    if(mYUVFrame->format==mVideoCodecCtx->pix_fmt){
                        if((ret = av_hwframe_transfer_data(mNV12Frame,mYUVFrame,0))<0){
                            continue;
                        }
                    }

                    sws_scale(mImgCtx,
                              (const uint8_t* const*)mNV12Frame->data,
                              (const int*)mNV12Frame->linesize,
                              0,
                              mNV12Frame->height,
                              mRGBFrame->data,mRGBFrame->linesize);

                    QImage img(mOutBuffer,
                               mVideoCodecCtx->width,mVideoCodecCtx->height,
                               QImage::Format_RGB32);
                    emit send_image(img);

                    QThread::msleep(30);
                }
            }
            av_packet_unref(mPkt);
        }
    }
    qDebug()<<"Thread stop now";
}

